/***************************************************************************
 *                                                                         *
 *   This file is part of the CrystalMem project,                          *
 *       http://code.google.com/p/crystalmem                               *
 *                                                                         *
 *   Copyright (C) 2008 by Alberto Scarpa <scarpa.alberto@gmail.com>       *
 *   Copyright (C) 2008 by Enrico Ros <enrico.ros@gmail.com>               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "MemoryTree.h"
#include <QDebug>

// Use Glew on Win32
#include <QtGlobal>
#ifdef Q_OS_WIN32
    #include <GL/glew.h>
#endif
#include <QGLContext>

//#define SUPPRESS_LOWMEM 1024
#define SUPPRESS_IDLE

#define notImplemented() { qWarning( "%s(%d): NOT IMPLEMENTED!", __FILE__, __LINE__ ); }

MemoryTree::MemoryTree()
{
    m_root = new ProgramNode( 0, 0 );
    m_root->fixed = true;
}

MemoryTree::~MemoryTree()
{
    notImplemented();
}

ProgramNode * MemoryTree::memoryAdded( const Backtrace & backTrace, quint32 size )
{
    QList<quint64> bt = backTrace;
    return m_root->memoryAdded( bt, size );
}

void MemoryTree::memoryRemoved( quint32 size, ProgramNode *node )
{
    node->memoryRemoved( size, 0 );
}

// TODO: Move physical updates out of here ??
//static QList<ProgramNode *> s_visibleNodes;
static void recursiveAffectChildren( ProgramNode * node )
{
    foreach ( ProgramNode * child, node->children ) {
#ifdef SUPPRESS_LOWMEM
        if ( child->totalMem < SUPPRESS_LOWMEM )
            continue;
#endif
#ifdef SUPPRESS_IDLE
        if ( !child->idleCounter )
            continue;
#endif

        // potential force (exponential)
        Vector3 childVector = child->pos - node->pos;
        double distance = childVector.module();
        double repulseModule = node->totalMem / distance;

        // attractive force (linear, spring-like)
        double attractModule = distance / 1.0;

        childVector.setModule( repulseModule - attractModule );
        child->force += childVector;
        node->force -= childVector;

        recursiveAffectChildren( child );
    }
}
/*
void addPotentialForce( const double energy )
{
    foreach ( ProgramNode * node1, s_visibleNodes )
        foreach ( ProgramNode * node2, s_visibleNodes )
            if ( node1 != node2 ) {
                Vector3 rVector = node2->pos - node1->pos;
                double distance = rVector.module();
                if ( distance == 0.0 )
                    continue;
                rVector.setModule( ((node1->totalMem * node2->totalMem) / sqrt(energy)) / distance );
                node1->force += rVector;
                node2->force -= rVector;
            }
}
*/
static void recursiveApplyForce( ProgramNode * node, double dT )
{
    // 0. dumping: force -= friction * object->velocity
    node->force -= (0.4 * node->vel);
    // 1. a(t) = f(t) / M;
    Vector3 acc = (node->force /*FIXME / node->weight*/);

    if ( !node->fixed ) {
        // 2B. v(t) = 0.5 * (a(t) + a(t-1)) * dt;
        node->vel += (acc * dT);
        // 2C. s(t) = (v(t-1) + v(t)) / 2 * dt;
        node->pos += (node->vel * dT);
    } else
        node->vel = Vector3::Null;

    // zero force for next iteration loop
    node->force = Vector3::Null;

    // recurse
    foreach ( ProgramNode * child, node->children ) {
#ifdef SUPPRESS_LOWMEM
        if ( node->totalMem < SUPPRESS_LOWMEM )
            continue;
#endif
#ifdef SUPPRESS_IDLE
        if ( !node->idleCounter )
            continue;
#endif
        recursiveApplyForce( child, dT );
    }
}

void MemoryTree::updateDynamics( double dT )
{
    // skip if no children
    if ( !m_root->totalMem )
        return;

    // sum repulsive potential and attractive spring forces
//    s_visibleNodes.clear();
    recursiveAffectChildren( m_root );
//    addPotentialForce( (double)m_root->totalMem );

    // apply phisics
    recursiveApplyForce( m_root, dT );
}

// TODO: Move Rendering out of here!
static void renderProgramNode( ProgramNode * node, const MemoryTree::RenderOpts & opts, int depth = 0 )
{
    // render up to NUMBER times if not refreshed
    if ( node->idleCounter )
        node->idleCounter--;
    double c = (double)node->idleCounter / 200.0;

    // render path to parent
    if ( node->parent ) {
        float dFactor = 1.0 - (float)depth / 16.0;
        glBegin( GL_LINES );
            glColor4f( dFactor, 0.0, 1.0 - dFactor, c );
            glVertex3f( node->parent->pos.x(), node->parent->pos.y(), node->parent->pos.z() );
            glColor4f( dFactor, 0.2, 1.0 - dFactor, c  );
            glVertex3f( node->pos.x(), node->pos.y(), node->pos.z() );
        glEnd();
    }

    // render node
    if ( node->localMem ) {
        double size = 1.0 + sqrt((double)node->localMem);
//        glColor3f( 0.5 + c/2, c, c );
        glColor4f( 1.0, 1.0, 1.0, c );
        glBegin( GL_LINES );
            glVertex3f( node->pos.x() - size, node->pos.y(), node->pos.z() );
            glVertex3f( node->pos.x() + size, node->pos.y(), node->pos.z() );
            glVertex3f( node->pos.x(), node->pos.y() - size, node->pos.z() );
            glVertex3f( node->pos.x(), node->pos.y() + size, node->pos.z() );
            glVertex3f( node->pos.x(), node->pos.y(), node->pos.z() - size );
            glVertex3f( node->pos.x(), node->pos.y(), node->pos.z() + size );
        glEnd();
    }

    // recurse into siblings
    foreach ( ProgramNode * child, node->children ) {
#ifdef SUPPRESS_LOWMEM
        if ( child->totalMem < SUPPRESS_LOWMEM )
            continue;
#endif
#ifdef SUPPRESS_IDLE
        if ( !node->idleCounter )
            continue;
#endif
        renderProgramNode( child, opts, depth + 1 );
    }
}

void MemoryTree::renderGL( const QRect & rect, RenderOpts opts )
{
    // TODO: setup context for rendering into rect (&restict viewport?)
    // 1. setup rendering
    glEnable( GL_DEPTH_TEST );
    glClear( GL_DEPTH_BUFFER_BIT );
    glEnable( GL_BLEND );

double width = rect.width();
double height = rect.height();

    // 1.1. set a perspective projection matrix to view the full scene
    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glLoadIdentity();
    gluPerspective( 90, width / height, 10.0, 10000.0 );

    // 1.2. set the modelview matrix for the usual ref-system (0,0 topleft -> downright)
    static double tFactor = 2 * tan( M_PI / 4.0 );
    glMatrixMode( GL_MODELVIEW );
    glPushMatrix();
    glLoadIdentity();
    glTranslatef( -width / 2, height / 2, -(height / tFactor) );
    glScalef( 1.0, -1.0, 1.0 );

    // setup auto-zoom, scale & c.
    glTranslatef( rect.x() + rect.width() / 2, rect.y() + rect.height() / 2, 0.0 );
    static int rot = 0; rot++;
    glRotatef( rot, 0, 1, 0 );
    glScalef( 0.2, 0.2, 0.2 );

    // recursively render
    glShadeModel( GL_SMOOTH );
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    renderProgramNode( m_root, opts );
    glDisable( GL_BLEND );
    glShadeModel( GL_FLAT );

    // reset GL state
    glMatrixMode( GL_MODELVIEW );
    glPopMatrix();
    glMatrixMode( GL_PROJECTION );
    glPopMatrix();
    glMatrixMode( GL_MODELVIEW );
    glDisable( GL_DEPTH_TEST );
}

void MemoryTree::printTree()
{
    m_root->print();
}
